🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / data / src / commonMain / kotlin / org / meshtastic / core / data / manager / MeshConnectionManagerImpl.kt
Displaying Raw • Download
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConnectionManagerImpl.kt e85300531e124df08788c1b765d50af1bf6d516d (e8530053) Text, 17.95 KB
T8b949e/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.Job
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.flow.first
Tff7b72import T7ee787kotlinx.coroutines.flow.launchIn
Tff7b72import T7ee787kotlinx.coroutines.flow.onEach
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.sync.Mutex
Tff7b72import T7ee787kotlinx.coroutines.sync.withLock
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787org.koin.core.annotation.Named
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.common.util.handledLaunch
Tff7b72import T7ee787org.meshtastic.core.common.util.nowMillis
Tff7b72import T7ee787org.meshtastic.core.common.util.nowSeconds
Tff7b72import T7ee787org.meshtastic.core.model.ConnectionState
Tff7b72import T7ee787org.meshtastic.core.model.DeviceType
Tff7b72import T7ee787org.meshtastic.core.model.TelemetryType
Tff7b72import T7ee787org.meshtastic.core.repository.AppWidgetUpdater
Tff7b72import T7ee787org.meshtastic.core.repository.CommandSender
Tff7b72import T7ee787org.meshtastic.core.repository.DataPair
Tff7b72import T7ee787org.meshtastic.core.repository.HandshakeConstants
Tff7b72import T7ee787org.meshtastic.core.repository.HistoryManager
Tff7b72import T7ee787org.meshtastic.core.repository.MeshConnectionManager
Tff7b72import T7ee787org.meshtastic.core.repository.MeshLocationManager
Tff7b72import T7ee787org.meshtastic.core.repository.MeshServiceNotifications
Tff7b72import T7ee787org.meshtastic.core.repository.MeshWorkerManager
Tff7b72import T7ee787org.meshtastic.core.repository.MqttManager
Tff7b72import T7ee787org.meshtastic.core.repository.NodeManager
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.PacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.PacketRepository
Tff7b72import T7ee787org.meshtastic.core.repository.PlatformAnalytics
Tff7b72import T7ee787org.meshtastic.core.repository.RadioConfigRepository
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceBroadcasts
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.repository.UiPrefs
Tff7b72import T7ee787org.meshtastic.proto.AdminMessage
Tff7b72import T7ee787org.meshtastic.proto.Config
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.ToRadio
Tff7b72import T7ee787kotlin.time.Duration.Companion.milliseconds
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds
Tff7b72import T7ee787kotlin.time.DurationUnit
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tf0883e@Single
Tff7b72class T56d364MeshConnectionManagerImplTb4b4b4(
Tff7b72private Tff7b72val Te6edf3radioInterfaceServiceTb4b4b4: Te6edf3RadioInterfaceServiceTb4b4b4,
Tff7b72private Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3serviceBroadcastsTb4b4b4: Te6edf3ServiceBroadcastsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3serviceNotificationsTb4b4b4: Te6edf3MeshServiceNotificationsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3uiPrefsTb4b4b4: Te6edf3UiPrefsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3packetHandlerTb4b4b4: Te6edf3PacketHandlerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3locationManagerTb4b4b4: Te6edf3MeshLocationManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3mqttManagerTb4b4b4: Te6edf3MqttManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3historyManagerTb4b4b4: Te6edf3HistoryManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioConfigRepositoryTb4b4b4: Te6edf3RadioConfigRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3commandSenderTb4b4b4: Te6edf3CommandSenderTb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeManagerTb4b4b4: Te6edf3NodeManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3analyticsTb4b4b4: Te6edf3PlatformAnalyticsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3packetRepositoryTb4b4b4: Te6edf3PacketRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3workerManagerTb4b4b4: Te6edf3MeshWorkerManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3appWidgetUpdaterTb4b4b4: Te6edf3AppWidgetUpdaterTb4b4b4,
Tf0883e@NamedTb4b4b4(Ta5d6ff"Ta5d6ffServiceScopeTa5d6ff"Tb4b4b4) Tff7b72private Tff7b72val Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3MeshConnectionManager Tb4b4b4{
T8b949e/**
* Serializes [onConnectionChanged] to prevent TOCTOU races when multiple coroutines emit state transitions
* concurrently (e.g. flow collector vs. sleep-timeout coroutine).
*/
Tff7b72private Tff7b72val Te6edf3connectionMutex Tff7b72= Te6edf3MutexTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72var Te6edf3sleepTimeoutTb4b4b4: Te6edf3Job? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3locationRequestsJobTb4b4b4: Te6edf3Job? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3handshakeTimeoutTb4b4b4: Te6edf3Job? Tff7b72= Tff7b72null
Tff7b72private Tff7b72var Te6edf3connectTimeMsec Tff7b72= T79c0ff0L
Tff7b72private Tff7b72var Te6edf3connectionRestored Tff7b72= Tff7b72false
Tff7b72init Tb4b4b4{
T8b949e// Bridge transport-level state into the canonical app-level state.
T8b949e// This is the ONLY consumer of RadioInterfaceService.connectionState — it applies
T8b949e// light-sleep policy and handshake awareness before writing to ServiceRepository.
Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3onEachTb4b4b4(Tff7b72::Te6edf3onRadioConnectionStateTb4b4b4)Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3scopeTb4b4b4)
T8b949e// Ensure notification title and content stay in sync with state changes
Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3updateStatusNotificationTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3scopeTb4b4b4)
Te6edf3scopeTb4b4b4.Te6edf3launch Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3appWidgetUpdaterTb4b4b4.Te6edf3updateAllTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to kickstart LocalStatsWidgetTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfo
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3myNodeEntity Tff7b72-Tff7b72>
Te6edf3locationRequestsJobTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3myNodeEntity Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3locationRequestsJob Tff7b72=
Te6edf3uiPrefs
Tb4b4b4.Te6edf3shouldProvideNodeLocationTb4b4b4(Te6edf3myNodeEntityTb4b4b4.Te6edf3myNodeNumTb4b4b4)
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3shouldProvide Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3shouldProvideTb4b4b4) Tb4b4b4{
Te6edf3locationManagerTb4b4b4.Te6edf3startTb4b4b4(Te6edf3scopeTb4b4b4) Tb4b4b4{ Te6edf3pos Tff7b72-Tff7b72> Te6edf3commandSenderTb4b4b4.Te6edf3sendPositionTb4b4b4(Te6edf3posTb4b4b4) Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3locationManagerTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3scopeTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3scopeTb4b4b4)
Tb4b4b4}
T8b949e/**
* Bridges a transport-level [ConnectionState] into the canonical app-level state.
*
* Applies light-sleep policy (power-saving / router role) to decide whether a [ConnectionState.DeviceSleep] event
* should be surfaced as sleep or as a full disconnect, then delegates to [onConnectionChanged] for the actual state
* transition.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffonRadioConnectionStateTb4b4b4(Te6edf3newStateTb4b4b4: Te6edf3ConnectionStateTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3localConfig Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3localConfigFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3isRouter Tff7b72= Te6edf3localConfigTb4b4b4.Te6edf3deviceTff7b72?.Te6edf3role Tff7b72=Tff7b72= Te6edf3ConfigTb4b4b4.Te6edf3DeviceConfigTb4b4b4.Te6edf3RoleTb4b4b4.Te6edf3ROUTER
Tff7b72val Te6edf3lsEnabled Tff7b72= Te6edf3localConfigTb4b4b4.Te6edf3powerTff7b72?.Te6edf3is_power_saving Tff7b72=Tff7b72= Tff7b72true Tff7b72|Tff7b72| Te6edf3isRouter
Tff7b72val Te6edf3effectiveState Tff7b72=
Tff7b72when Tb4b4b4(Te6edf3newStateTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Connected Tff7b72-Tff7b72> Te6edf3ConnectionStateTb4b4b4.Te6edf3Connected
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3DeviceSleep Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3lsEnabledTb4b4b4) Te6edf3ConnectionStateTb4b4b4.Te6edf3DeviceSleep Tff7b72else Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Connecting Tff7b72-Tff7b72> Te6edf3ConnectionStateTb4b4b4.Te6edf3Connecting
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected Tff7b72-Tff7b72> Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected
Tb4b4b4}
Te6edf3onConnectionChangedTb4b4b4(Te6edf3effectiveStateTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffonConnectionChangedTb4b4b4(Te6edf3cTb4b4b4: Te6edf3ConnectionStateTb4b4b4) Tff7b72= Te6edf3connectionMutexTb4b4b4.Te6edf3withLock Tb4b4b4{
Tff7b72val Te6edf3current Tff7b72= Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value
Tff7b72if Tb4b4b4(Te6edf3current Tff7b72=Tff7b72= Te6edf3cTb4b4b4) Tff7b72returnTf0883e@withLock
T8b949e// If the transport reports 'Connected', but we are already in the middle of a handshake (Connecting)
Tff7b72if Tb4b4b4(Te6edf3c Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Connected Tff7b72&Tff7b72& Te6edf3current Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectingTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring redundant transport connection signal while handshake is in progressTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@withLock
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffonConnectionChanged: Tffd700$Te6edf3currentTa5d6ff -> Tffd700$Te6edf3cTa5d6ff" Tb4b4b4}
Te6edf3sleepTimeoutTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3sleepTimeout Tff7b72= Tff7b72null
Te6edf3handshakeTimeoutTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3handshakeTimeout Tff7b72= Tff7b72null
Tff7b72when Tb4b4b4(Te6edf3cTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Connecting Tff7b72-Tff7b72> Te6edf3serviceRepositoryTb4b4b4.Te6edf3setConnectionStateTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectingTb4b4b4)
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Connected Tff7b72-Tff7b72> Te6edf3handleConnectedTb4b4b4(Tb4b4b4)
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3DeviceSleep Tff7b72-Tff7b72> Te6edf3handleDeviceSleepTb4b4b4(Tb4b4b4)
Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3Disconnected Tff7b72-Tff7b72> Te6edf3handleDisconnectedTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhandleConnectedTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Track whether this connection was restored from device sleep (vs. a fresh connect),
T8b949e// matching Apple's "connectionRestored" attribute for cross-platform DataDog parity.
Te6edf3connectionRestored Tff7b72= Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3DeviceSleep
T8b949e// The service state remains 'Connecting' until config is fully loaded
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4) Tb4b4b4{
Te6edf3serviceRepositoryTb4b4b4.Te6edf3setConnectionStateTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectingTb4b4b4)
Tb4b4b4}
Te6edf3serviceBroadcastsTb4b4b4.Te6edf3broadcastConnectionTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffStarting mesh handshake (Stage 1)Ta5d6ff" Tb4b4b4}
Te6edf3connectTimeMsec Tff7b72= Te6edf3nowMillis
Te6edf3startConfigOnlyTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffstartHandshakeStallGuardTb4b4b4(Te6edf3stageTb4b4b4: Tffa657IntTb4b4b4, Te6edf3actionTb4b4b4: Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3handshakeTimeoutTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3handshakeTimeout Tff7b72=
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3HANDSHAKE_TIMEOUTTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectingTb4b4b4) Tb4b4b4{
T8b949e// Attempt one retry. Note: the firmware silently drops identical consecutive
T8b949e// writes (per-connection dedup). If the first want_config_id was received and
T8b949e// the stall is on our side, the retry will be dropped and the reconnect below
T8b949e// will trigger instead — which is the right recovery in that case.
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{
Ta5d6ff"Ta5d6ffHandshake stall detected at Stage Tffd700$Te6edf3stageTa5d6ff — retrying, then reconnecting if still stalledTa5d6ff"
Tb4b4b4}
Te6edf3actionTb4b4b4(Tb4b4b4)
Te6edf3delayTb4b4b4(Te6edf3HANDSHAKE_RETRY_TIMEOUTTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3value Tff7b72is Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectingTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3e Tb4b4b4{ Ta5d6ff"Ta5d6ffHandshake still stalled after retry, forcing reconnectTa5d6ff" Tb4b4b4}
Te6edf3onConnectionChangedTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8fftearDownConnectionTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3packetHandlerTb4b4b4.Te6edf3stopPacketQueueTb4b4b4(Tb4b4b4)
Te6edf3commandSenderTb4b4b4.Te6edf3setSessionPasskeyTb4b4b4(Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4) T8b949e// Prevent stale passkey on reconnect.
Te6edf3locationManagerTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)
Te6edf3mqttManagerTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhandleDeviceSleepTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3serviceRepositoryTb4b4b4.Te6edf3setConnectionStateTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3DeviceSleepTb4b4b4)
Te6edf3tearDownConnectionTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3connectTimeMsec Tff7b72!Tff7b72= T79c0ff0LTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3now Tff7b72= Te6edf3nowMillis
Tff7b72val Te6edf3duration Tff7b72= Te6edf3now Tff7b72- Te6edf3connectTimeMsec
Te6edf3connectTimeMsec Tff7b72= T79c0ff0L
Te6edf3analyticsTb4b4b4.Te6edf3trackTb4b4b4(
Te6edf3EVENT_CONNECTED_SECONDSTb4b4b4,
Te6edf3DataPairTb4b4b4(Te6edf3EVENT_CONNECTED_SECONDSTb4b4b4, Te6edf3durationTb4b4b4.Te6edf3millisecondsTb4b4b4.Te6edf3toDoubleTb4b4b4(Te6edf3DurationUnitTb4b4b4.Te6edf3SECONDSTb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3sleepTimeout Tff7b72=
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3localConfig Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3localConfigFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3rawTimeout Tff7b72= Tb4b4b4(Te6edf3localConfigTb4b4b4.Te6edf3powerTff7b72?.Te6edf3ls_secs Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72+ Te6edf3DEVICE_SLEEP_TIMEOUT_SECONDS
T8b949e// Cap the timeout so routers or power-saving configs (ls_secs=3600) don't
T8b949e// leave the UI stuck in DeviceSleep for over an hour.
Tff7b72val Te6edf3timeout Tff7b72= Te6edf3rawTimeoutTb4b4b4.Te6edf3coerceAtMostTb4b4b4(Te6edf3MAX_SLEEP_TIMEOUT_SECONDSTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffWaiting for sleeping device, timeout=Tffd700$Te6edf3timeoutTa5d6ff secs (raw=Tffd700$Te6edf3rawTimeoutTa5d6ff)Ta5d6ff" Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3timeoutTb4b4b4.Te6edf3secondsTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffDevice timed out, setting disconnectedTa5d6ff" Tb4b4b4}
Te6edf3onConnectionChangedTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffdevice sleep timeout cancelledTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3serviceBroadcastsTb4b4b4.Te6edf3broadcastConnectionTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhandleDisconnectedTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3serviceRepositoryTb4b4b4.Te6edf3setConnectionStateTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4)
Te6edf3tearDownConnectionTb4b4b4(Tb4b4b4)
Te6edf3analyticsTb4b4b4.Te6edf3trackTb4b4b4(
Te6edf3EVENT_MESH_DISCONNECTTb4b4b4,
Te6edf3DataPairTb4b4b4(Te6edf3KEY_NUM_NODESTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4,
Te6edf3DataPairTb4b4b4(Te6edf3KEY_NUM_ONLINETb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3count Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isOnline Tb4b4b4}Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3analyticsTb4b4b4.Te6edf3trackTb4b4b4(Te6edf3EVENT_NUM_NODESTb4b4b4, Te6edf3DataPairTb4b4b4(Te6edf3KEY_NUM_NODESTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4)
Te6edf3serviceBroadcastsTb4b4b4.Te6edf3broadcastConnectionTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffstartConfigOnlyTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3action Tff7b72= Tb4b4b4{ Te6edf3packetHandlerTb4b4b4.Te6edf3sendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3startHandshakeStallGuardTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3actionTb4b4b4)
Te6edf3actionTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffstartNodeInfoOnlyTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3action Tff7b72= Tb4b4b4{ Te6edf3packetHandlerTb4b4b4.Te6edf3sendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3startHandshakeStallGuardTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3actionTb4b4b4)
Te6edf3actionTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffonRadioConfigLoadedTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72val Te6edf3queuedPackets Tff7b72= Te6edf3packetRepositoryTb4b4b4.Te6edf3getQueuedPacketsTb4b4b4(Tb4b4b4) Tff7b72?: Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3queuedPacketsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3packet Tff7b72-Tff7b72>
Tff7b72try Tb4b4b4{
Te6edf3workerManagerTb4b4b4.Te6edf3enqueueSendMessageTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to enqueue queued packet workerTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffonNodeDbReadyTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3handshakeTimeoutTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3handshakeTimeout Tff7b72= Tff7b72null
Tff7b72val Te6edf3myNodeNum Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNumTb4b4b4.Te6edf3value Tff7b72?: T79c0ff0
T8b949e// Set device time now that the full node picture is ready. Sending this during Stage 1
T8b949e// (onRadioConfigLoaded) introduced GATT write contention with the Stage 2 node-info burst.
Te6edf3commandSenderTb4b4b4.Te6edf3sendAdminTb4b4b4(Te6edf3myNodeNumTb4b4b4) Tb4b4b4{ Te6edf3AdminMessageTb4b4b4(Te6edf3set_time_only Tff7b72= Te6edf3nowSecondsTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e// Proactively seed the session passkey. The firmware embeds session_passkey in every
T8b949e// admin *response* (wantResponse=true), but set_time_only has no response. A get_owner
T8b949e// request is the lightest way to trigger a response and populate the passkey cache so
T8b949e// that subsequent write operations don't fail with ADMIN_BAD_SESSION_KEY.
Te6edf3commandSenderTb4b4b4.Te6edf3sendAdminTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3wantResponse Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4{ Te6edf3AdminMessageTb4b4b4(Te6edf3get_owner_request Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
T8b949e// Start MQTT if enabled
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72val Te6edf3moduleConfig Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3moduleConfigFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Te6edf3mqttManagerTb4b4b4.Te6edf3startProxyTb4b4b4(
Te6edf3moduleConfigTb4b4b4.Te6edf3mqttTff7b72?.Te6edf3enabled Tff7b72=Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3moduleConfigTb4b4b4.Te6edf3mqttTff7b72?.Te6edf3proxy_to_client_enabled Tff7b72=Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3reportConnectionTb4b4b4(Tb4b4b4)
T8b949e// Request history
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72val Te6edf3moduleConfig Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3moduleConfigFlowTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Te6edf3moduleConfigTb4b4b4.Te6edf3store_forwardTff7b72?.Te6edf3let Tb4b4b4{
Te6edf3historyManagerTb4b4b4.Te6edf3requestHistoryReplayTb4b4b4(Ta5d6ff"Ta5d6ffonNodeDbReadyTa5d6ff"Tb4b4b4, Te6edf3myNodeNumTb4b4b4, Tffa657itTb4b4b4, Ta5d6ff"Ta5d6ffUnknownTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// Request immediate LocalStats and DeviceMetrics update on connection with proper request IDs
Te6edf3commandSenderTb4b4b4.Te6edf3requestTelemetryTb4b4b4(Te6edf3commandSenderTb4b4b4.Te6edf3generatePacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4, Te6edf3TelemetryTypeTb4b4b4.Te6edf3LOCAL_STATSTb4b4b4.Te6edf3ordinalTb4b4b4)
Te6edf3commandSenderTb4b4b4.Te6edf3requestTelemetryTb4b4b4(Te6edf3commandSenderTb4b4b4.Te6edf3generatePacketIdTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4, Te6edf3TelemetryTypeTb4b4b4.Te6edf3DEVICETb4b4b4.Te6edf3ordinalTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffreportConnectionTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNode Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3getMyNodeInfoTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3radioModel Tff7b72= Te6edf3DataPairTb4b4b4(Te6edf3KEY_RADIO_MODELTb4b4b4, Te6edf3myNodeTff7b72?.Te6edf3model Tff7b72?: Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)
Te6edf3analyticsTb4b4b4.Te6edf3trackTb4b4b4(
Te6edf3EVENT_MESH_CONNECTTb4b4b4,
Te6edf3DataPairTb4b4b4(Te6edf3KEY_NUM_NODESTb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4,
Te6edf3DataPairTb4b4b4(Te6edf3KEY_NUM_ONLINETb4b4b4, Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3count Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isOnline Tb4b4b4}Tb4b4b4)Tb4b4b4,
Te6edf3radioModelTb4b4b4,
Tb4b4b4)
T8b949e// DataDog RUM custom action matching Apple's "connect" event for cross-platform analytics.
Tff7b72val Te6edf3transportType Tff7b72= Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3getDeviceAddressTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3DeviceTypeTb4b4b4.Te6edf3fromAddressTb4b4b4(Tffa657itTb4b4b4)Tff7b72?.Te6edf3name Tb4b4b4}
Te6edf3analyticsTb4b4b4.Te6edf3trackConnectTb4b4b4(
Te6edf3firmwareVersion Tff7b72= Te6edf3myNodeTff7b72?.Te6edf3firmwareVersionTb4b4b4,
Te6edf3transportType Tff7b72= Te6edf3transportTypeTb4b4b4,
Te6edf3hardwareModel Tff7b72= Te6edf3myNodeTff7b72?.Te6edf3modelTb4b4b4,
Te6edf3nodes Tff7b72= Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3connectionRestored Tff7b72= Te6edf3connectionRestoredTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffupdateTelemetryTb4b4b4(Te6edf3tTb4b4b4: Te6edf3TelemetryTb4b4b4) Tb4b4b4{
Te6edf3tTb4b4b4.Te6edf3local_statsTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3updateLocalStatsTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Te6edf3updateStatusNotificationTb4b4b4(Te6edf3tTb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffupdateStatusNotificationTb4b4b4(Te6edf3telemetryTb4b4b4: Te6edf3Telemetry?Tb4b4b4) Tb4b4b4{
Te6edf3serviceNotificationsTb4b4b4.Te6edf3updateServiceStateNotificationTb4b4b4(
Te6edf3serviceRepositoryTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3valueTb4b4b4,
Te6edf3telemetry Tff7b72= Te6edf3telemetryTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3DEVICE_SLEEP_TIMEOUT_SECONDS Tff7b72= T79c0ff3T79c0ff0
T8b949e// Maximum time (in seconds) to wait for a sleeping device before declaring it
T8b949e// disconnected, regardless of the device's ls_secs configuration. Without this
T8b949e// cap, routers (ls_secs=3600) leave the UI in DeviceSleep for over an hour.
Tff7b72private Tff7b72const Tff7b72val Te6edf3MAX_SLEEP_TIMEOUT_SECONDS Tff7b72= T79c0ff3T79c0ff0T79c0ff0
Tff7b72private Tff7b72val Te6edf3HANDSHAKE_TIMEOUT Tff7b72= T79c0ff3T79c0ff0.Te6edf3seconds
T8b949e// Shorter window for the retry attempt: if the device genuinely didn't receive the
T8b949e// first want_config_id the retry completes within a few seconds. Waiting another 30s
T8b949e// before reconnecting just delays recovery unnecessarily.
Tff7b72private Tff7b72val Te6edf3HANDSHAKE_RETRY_TIMEOUT Tff7b72= T79c0ff1T79c0ff5.Te6edf3seconds
Tff7b72private Tff7b72const Tff7b72val Te6edf3EVENT_CONNECTED_SECONDS Tff7b72= Ta5d6ff"Ta5d6ffconnected_secondsTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3EVENT_MESH_DISCONNECT Tff7b72= Ta5d6ff"Ta5d6ffmesh_disconnectTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3EVENT_NUM_NODES Tff7b72= Ta5d6ff"Ta5d6ffnum_nodesTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3EVENT_MESH_CONNECT Tff7b72= Ta5d6ff"Ta5d6ffmesh_connectTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3KEY_NUM_NODES Tff7b72= Ta5d6ff"Ta5d6ffnum_nodesTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3KEY_NUM_ONLINE Tff7b72= Ta5d6ff"Ta5d6ffnum_onlineTa5d6ff"
Tff7b72private Tff7b72const Tff7b72val Te6edf3KEY_RADIO_MODEL Tff7b72= Ta5d6ff"Ta5d6ffradio_modelTa5d6ff"
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.1s